package middleware

import (
	"basic-go/webook/internal/web"
	"encoding/gob"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

type LoginJWTMiddlewareBuilder struct {
}

func (m *LoginJWTMiddlewareBuilder) CheckLogin() gin.HandlerFunc {
	// 注册一下这个类型
	gob.Register(time.Now())
	return func(ctx *gin.Context) {
		path := ctx.Request.URL.Path
		if path == "/users/signup" ||
			path == "/users/login" ||
			path == "/users/login_sms/code/send" ||
			path == "/users/login_sms" {
			// 不需要登录校验
			return
		}
		// 根据约定,token在Authorization头部
		// Bearer XXX
		authCode := ctx.GetHeader("Authorization")
		if authCode == "" {
			// 没登录,没有token,Authorization这个头部也没有
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		segs := strings.Split(authCode, " ")
		if len(segs) != 2 {
			// 没登录,Authorization中的内容是乱传的
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		tokenStr := segs[1]
		var uc web.UserCliams
		token, err := jwt.ParseWithClaims(tokenStr, &uc, func(token *jwt.Token) (interface{}, error) {
			return web.JWTKey, nil
		})
		if err != nil {
			// token不对,token是伪造的
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		if !token.Valid {
			// token解析出来了,但是token可能是非法的,或者过期了
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		// if uc.UserAgent != ctx.GetHeader("User-Agent") {
		// 	// 后期我们讲到了监控告警的时候,这个地方要埋点
		// 	// 能够进来这个分支的,大概率是攻击者
		// 	ctx.AbortWithStatus(http.StatusUnauthorized)
		// 	return
		// }

		// INFO: 如下是token验证成功后执行的代码

		// 检查是否需要刷新token
		expireTime := uc.ExpiresAt
		// 假设token的过期时间是60秒,那么当<50秒时,
		// 只要有访问,就可以刷新token,这个时候,过期时间又变为60了,
		// 后续的token都不会刷新了(直到过期时间变为<50秒时)
		if expireTime.Sub(time.Now()) < time.Second*50 {
			uc.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 30))
			tokenStr, err = token.SignedString(web.JWTKey)
			ctx.Header("x-jwt-token", tokenStr)
			if err != nil {
				// 这边不要中断,因为仅仅是过期时间没有刷新,但是用户还是登录了的
				log.Println(err)
			}
		}

		// 将uc添加到上下文中,供后续业务代码中使用
		ctx.Set("user", uc)
		// IMPT: 这里不调用ctx.Next()是可以的
		// 因为中间件的目的是在处理请求前进行用户认证,
		// 一旦发现认证失败，就通过ctx.AbortWithStatus()结束请求处理，
		// 而在认证成功的情况下，中间件自然结束，允许请求继续传递至下一个中间件或处理函数
	}
}
